#include <jni.h>
#include <string>
#include <pthread.h>
#include <android/log.h>
#include <iostream>

#define TAG "native-lib"

#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG,  __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG,  __VA_ARGS__);
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG,  __VA_ARGS__);

JavaVM *javaVm;
jobject thiz;

extern "C"
//JNIEXPORT作用是保证可以在其他项目中被调用
//jstring 是返回值
//JINCALL 只在window下有用，定义函数入栈的规则和清理规则
JNIEXPORT jstring JNICALL
//当native（java中的声明）方法为静态（static）时，第二个参数为jclass代表native方法所属的class对象
//当native方法为非静态时，第二个参数为jobject代表native方法所属的对象
native_test_base_type(JNIEnv *env, jobject thiz,
                      jint i, jlong l,
                      jfloat f, jdouble d,
                      jboolean b,
                      jstring s) {
    //jboolean 类型实际是无符号的char类型， 定义了两个值，JNI_FALSE(0)和JNI_TRUE(1)
    //jint 其实就是int类型， 只是起了个别名，其他的jxxx类型同理
    LOGD("int类型的值: %d", i);
    LOGD("long类型的值: %d", l);
    LOGD("float类型的值: %f", f);
    LOGD("double类型的值: %f", d);
    if (b) {
        LOGD("boolean类型的值: true");
    } else {
        LOGD("boolean类型的值: false");
    }

    //jstring是java中的类型，在c++中无法直接使用，c++中用char数组表示字符串,需要转换后才能使用
    const char *cs = env->GetStringUTFChars(s, NULL);
    LOGD("string类型的值: %s", cs);
    env->ReleaseStringUTFChars(s, cs);
    //std::string 是c++11中定义的一种字符串容器，用法相当于java中的string
    std::string str = "测试jni 基础类型";
    return env->NewStringUTF(str.c_str());
}

extern "C"
JNIEXPORT jstring JNICALL
native_test_array(JNIEnv *env, jobject thiz, jintArray arr_int,
                  jobjectArray arr_string) {

    LOGD("遍历数组开始");
    //遍历数组,int等基础类型的数组有对应类型的转换方法，都是差不多的步骤。
    //jintArray是java中int数组的地址，无法直接在c++中使用，需先转为jint*
    jint *arr = env->GetIntArrayElements(arr_int, NULL);
    int size = env->GetArrayLength(arr_int);
    for (int i = 0; i < size; i++) {
        LOGD("第%d个: %d", i, arr[i]);
        //修改
        arr[i] = i * i;
    }
    //释放c++数组,并决定是否同步java的数组。 最后的mode有以下值
    //模式 0 (NULL): 刷新 Java 数组 , 释放 C/C++ 数组
    //模式 1 ( JNI_COMMIT ) : 刷新 Java 数组 , 不释放 C/C ++ 数组
    //模式 2 ( JNI_ABORT ) : 不刷新 Java 数组 , 释放 C/C++ 数组
    env->ReleaseIntArrayElements(arr_int, arr, NULL);

    LOGD("遍历字符串数组");
    //因为c++中没有java对应的string类型，所以string类型是当做object处理的
    jint sSize = env->GetArrayLength(arr_string);
    for (int i = 0; i < sSize; i++) {
        //对于object类型的数组，jni只能一个一个的取出
        jobject item = env->GetObjectArrayElement(arr_string, i);
        //先将jobject转为jstring, 使用static_cast 进行子类、父类的转换。
        jstring js = static_cast<jstring>(item);
        //在将jstring转为char*
        const char *strItem = env->GetStringUTFChars(js, NULL);
        LOGD("第%d个字符串: %s", i, strItem);

        //修改
        std::string es = "这是修改后的第";
        es += (char) ('0' + i);
        es += "个字符串";
        jstring editStr = env->NewStringUTF(es.c_str());
        env->SetObjectArrayElement(arr_string, i, editStr);

        //释放，一般get和release是配套使用的，newStringUtf如果是只在本地使用的最好使用DeleteLocalRef释放。
        env->ReleaseStringUTFChars(js, strItem);
    }


    LOGD("遍历数组结束");
    std::string str = "测试jni 数组类型";
    return env->NewStringUTF(str.c_str());
}

extern "C"
JNIEXPORT jstring JNICALL
native_test_object(JNIEnv *env, jobject thiz, jobject test_bean) {
    //测试类的调用、访问
    //先获取到jclass对象, 有两种方式
//    jclass clazz = env->FindClass("com/example/ndktest2/TestBean");
    jclass clazz = env->GetObjectClass(test_bean);
    //读取属性, 不管是私有还是保护还是公有的都可以通过一下方法获取
    //私有
    jfieldID id_id = env->GetFieldID(clazz, "id", "I");
    jint id = env->GetIntField(test_bean, id_id);
    LOGD("TestBean id: %d", id);
    //修改id
    env->SetIntField(test_bean, id_id, 11);
    //保护
    jfieldID count_id = env->GetFieldID(clazz, "count", "I");
    jint count = env->GetIntField(test_bean, id_id);
    LOGD("TestBean count: %d", count);
    //公有
    jfieldID age_id = env->GetFieldID(clazz, "age", "I");
    jint age = env->GetIntField(test_bean, age_id);
    LOGD("TestBean age: %d", id);

    //获取string类型
    jfieldID title_id = env->GetFieldID(clazz, "title", "Ljava/lang/String;");
    jobject titleF = env->GetObjectField(test_bean, title_id);
    jstring titleStr = static_cast<jstring>(titleF);
    const char *title = env->GetStringUTFChars(titleStr, NULL);
    LOGD("TestBean title: %s", title)
    env->ReleaseStringUTFChars(titleStr, title);
    //修改string类型
    jstring newTitleStr = env->NewStringUTF("jni中修改的title");
    env->SetObjectField(test_bean, title_id, newTitleStr);

    //调用方法
    //没有参数， 没有返回值
    jmethodID test_m_id = env->GetMethodID(clazz, "jniTest", "()V");
    env->CallVoidMethod(test_bean, test_m_id);
    //有参数，没有返回值
    jmethodID setAddress_id = env->GetMethodID(clazz, "setAddress", "(Ljava/lang/String;)V");
    env->CallVoidMethod(test_bean, setAddress_id, env->NewStringUTF("这是jni设置的地址"));
    jmethodID getId_id = env->GetMethodID(clazz, "getId", "()I");
    jint id2 = env->CallIntMethod(test_bean, getId_id);
    LOGD("getId: %d", id2);

    std::string str = "测试jni Class类型";
    return env->NewStringUTF(str.c_str());
}


extern "C"
JNIEXPORT jobject JNICALL
native_testNewTestBean(JNIEnv *env, jobject thiz) {
    jclass clazz = env->FindClass("com/example/ndktest2/TestBean");
    //实例化，有两种
    //不调用构造函数
//    jobject bean = env->AllocObject(clazz);
    //通过构造函数
    jmethodID init_id = env->GetMethodID(clazz, "<init>", "()V");
    jobject bean = env->NewObject(clazz, init_id);
//    env->NewWeakGlobalRef(bean);

    jfieldID id_id = env->GetFieldID(clazz, "id", "I");
    env->SetIntField(bean, id_id, 22);

    jfieldID name_id = env->GetFieldID(clazz, "name", "Ljava/lang/String;");
    env->SetObjectField(bean, name_id, env->NewStringUTF("jin生成的类"));

    //释放后返回的就是null
//    env->DeleteLocalRef(bean);
    return bean;
}

extern "C"
JNIEXPORT void JNICALL
native_testException(JNIEnv *env, jobject thiz) {

    jclass clazz = env->GetObjectClass(thiz);
    jmethodID throw_id = env->GetMethodID(clazz, "throwsException", "()V");
    env->CallVoidMethod(thiz, throw_id);

    // 检测是否发生异常。并返回异常信息
    jthrowable exc = env->ExceptionOccurred();
    //只会返回是否有异常
    //env->ExceptionCheck();
    if (exc) {//如果发生异常
        env->ExceptionDescribe(); // 打印异常信息
        env->ExceptionClear(); // 清除掉发生的异常

        // 抛出异常
        jclass newExcCls = env->FindClass("java/lang/IllegalArgumentException");
        env->ThrowNew(newExcCls, "JNI 中发生了一个异常信息"); // 返回一个新的异常到 Java
    }

}


void *threadRun(void *arg) {
    JNIEnv *env;

    //将线程附加到 Java 虚拟机中来获取jnienv
    javaVm->AttachCurrentThread(&env, 0);
    //同步
    if( JNI_OK != env->MonitorEnter(thiz)){
        LOGE("MonitorEnter failed");
        //解除附加
        javaVm->DetachCurrentThread();
        return NULL;
    }

    LOGD("当前子线程 : %d", std::this_thread::get_id());

    if( JNI_OK != env->MonitorExit(thiz)){
        LOGE("MonitorExit failed");
    }

    //解除附加
    javaVm->DetachCurrentThread();
    return 0;
}

extern "C"
JNIEXPORT void JNICALL
native_testThread(JNIEnv *env, jobject thiz) {
    LOGD("当前线程 : %d", std::this_thread::get_id());

    ::thiz = env->NewGlobalRef(thiz);
    // 如果是非全局的，函数一结束，就被释放了
    pthread_t p_id;
    pthread_create(&p_id, 0, threadRun, thiz);
    pthread_join(p_id, 0);

    env->DeleteGlobalRef(::thiz);
}

/* 源码结构体
 * typedef struct {
    const char* name;
    const char* signature;
    void*       fnPtr;
    } JNINativeMethod;
 */
/**
 *  函数签名：
    (参数列表)返回值
    例如: (IZ)V 表示参数是int、boolean，返回void的函数
 *
 *  类型描述
    boolean Z
    byte B
    char C
    short S
    int I
    long J
    float F
    double D
    void V
    其它引用类型 L+全类名+; 例如string：Ljava/lang/String;
    数组 [+类型描述 例如：[I 表示int数组
 */
static const JNINativeMethod jniNativeMethod[] = {
        {"testBaseType",    "(IJFDZLjava/lang/String;)Ljava/lang/String;",
                (void *) native_test_base_type},

        {"testArray",       "([I[Ljava/lang/String;)Ljava/lang/String;",
                (void *) native_test_array},

        {"testObjectType",  "(Lcom/wjj/ndkdemo/TestBean;)Ljava/lang/String;",
                (void *) native_test_object},

        {"testNewTestBean", "()Lcom/wjj/ndkdemo/TestBean;",
                (void *) native_testNewTestBean},

        {"testException",   "()V",
                (void *) native_testException},

        {"testThread",      "()V",
                (void *) native_testThread},
};

/**
 * 该函数定义在jni.h头文件中，System.loadLibrary()时会调用JNI_OnLoad()函数
 */
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *javaVm, void *pVoid) {
    ::javaVm = javaVm;

    //通过虚拟机 创建全新的 evn
    JNIEnv *jniEnv = nullptr;
    jint result = javaVm->GetEnv(reinterpret_cast<void **>(&jniEnv), JNI_VERSION_1_6);
    if (result != JNI_OK) {
        return JNI_ERR; // 主动报错
    }

    jclass jniTesterClass = jniEnv->FindClass("com/wjj/ndkdemo/JniTester");
    if (jniTesterClass == nullptr) {
        LOGE("Could not find com/wjj/ndkdemo/JniTester")
        return JNI_ERR;
    }
    jniEnv->RegisterNatives(jniTesterClass, jniNativeMethod,
                            sizeof(jniNativeMethod) / sizeof(jniNativeMethod[0]));//动态注册的数量
    return JNI_VERSION_1_6;
}
